package se.webbzon.boltzmann;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowStateListener;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;
import javax.swing.WindowConstants;

import se.webbzon.boltzmann.camera.Camera;
import se.webbzon.boltzmann.configuration.Controls;
import se.webbzon.boltzmann.configuration.Graphics;
import se.webbzon.boltzmann.configuration.Graphics.Resolution;
import se.webbzon.boltzmann.configuration.GraphicsListener;
import se.webbzon.boltzmann.debug.Debug;
import se.webbzon.boltzmann.environment.AbstractEnvironment;
import se.webbzon.boltzmann.environment.DefaultEnvironment;
import se.webbzon.boltzmann.escaper.Escaper;
import se.webbzon.boltzmann.game.GameConfiguration;
import se.webbzon.boltzmann.input.KeyboardInput;
import se.webbzon.boltzmann.menu.GameMenu;
import se.webbzon.boltzmann.menu.GameMenuLayoutHigh;
import se.webbzon.boltzmann.menu.GameMenuLayoutLow;
import se.webbzon.boltzmann.menu.GameMenuLayoutMedium;
import se.webbzon.boltzmann.minigame.Minigame;
import se.webbzon.boltzmann.minigame.botany.BotanyGame;
import se.webbzon.boltzmann.player.PlayerConfiguration;
import se.webbzon.boltzmann.world.DefaultWorldLoader;
import se.webbzon.oschi01.worldarea.WorldArea;
import se.webbzon.oschi01.worldarea.WorldEngine;
import se.webbzon.oschi01.worldcamera.DefaultWorldView;
import se.webbzon.oschi01.worldcamera.WorldViewFactory;

public class GameWindow extends JFrame {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = -5511138810119239078L;
	
	// The window in maximized state
	private static final int MAXIMIZED = 6;

	// The dimension of the window
	private final Dimension d;
	
	// The world view factory
	private final WorldViewFactory factory;
	
	// The keyboard input
	private final KeyboardInput keyboard; 
	
	// The configuration of the game
	private final GameConfiguration config;
	
	// The world engine
	private final WorldEngine engine;
	
	// The current world area and world view
	private DefaultWorldView view;
	
	// The camera
	private Camera camera;
	
	// The game menu
	private GameMenu menu;
	
	/*============================================================
	Constructors
	============================================================*/
	
	public GameWindow(WorldViewFactory factory, WorldEngine engine, KeyboardInput keyboard) {
		this.d = new Dimension();
		this.factory = factory;
		this.engine = engine;
		this.keyboard = keyboard;
		this.config = new GameConfiguration();
		
		Graphics.instance().getDimension(d);
		Graphics.instance().addListener(new GraphicsListenerImpl());
		getContentPane().setBackground(Color.black);
		
		Camera camera = null;
		try {
			AbstractEnvironment env = new DefaultEnvironment();
			WorldArea world = new DefaultWorldLoader(env,"misc/main").load();
			camera = world.findInstances(Camera.class).iterator().next();
			engine.setWorld(world);
		} catch (Exception e) {
			Debug.report(e);
		}
		this.camera = camera;
		
		// Instantiate the view
		reinstantiate();
		
		/*Minigame minigame = new BotanyGame();
		minigame.start(engine.getWorld(),view,Controls.instance().getInput());*/
		
		config.addPlayer(new PlayerConfiguration(view,keyboard));
		
		setCursor(getToolkit().createCustomCursor( new BufferedImage( 1, 1, BufferedImage.TYPE_INT_ARGB ), new Point(), null ) );
		setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
		addWindowStateListener(new WindowListenerImpl());
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	/** Returns the WorldArea **/
	public Escaper getEscape() {
		return new EscaperImpl();
	}
	
	/*============================================================
	Private Methods
	============================================================*/
	
	/** Returns true if the given property is set in the given properties. **/
	private static final boolean isSet(int properties, int property) {
		return ((properties & property) == property);
	}
	
	/** Creates a new game menu with the provided image observer. **/
	private GameMenu createMenu(Component parent) {
		switch (Graphics.instance().getResolution()) {
		case LOW: return new GameMenu(parent, new GameMenuLayoutLow(),config);
		case MEDIUM: return new GameMenu(parent, new GameMenuLayoutMedium(),config);
		default:
			return new GameMenu(parent, new GameMenuLayoutHigh(),config);
		}
	}
	
	/** Reinstantiates the window with the new dimension. **/
	private final void reinstantiate() {
		WorldArea world = engine.getWorld();
		setVisible(false);
		if (view != null) {
			remove(view);
			camera.removeView(view);
		}
		view = factory.makeView(d);
		view.setBackground(Color.black);
		view.addKeyListener(keyboard);
		view.addMouseListener(keyboard);
		view.addMouseMotionListener(keyboard);
		view.addComponentListener(new ComponentListenerImpl());
		if (menu != null) {
			world.destroyInstance(menu);
		}
		menu = createMenu(view);
		world.createInstance(menu);
		view.getCamera().setProjectionMaxDistance(1024);
		view.requestFocus();
		add(view);
		camera.addView(view);
		pack();
		setLocationRelativeTo(null);
		setVisible(true);
		
		if (config.players() > 0)
			config.getPlayer(0).setView(view);
	}
	
	/*============================================================
	Private Methods
	============================================================*/
	
	/** Sets the size of the view to the **/
	private final void setPreferredViewSize(Dimension d) {
		final double w, h;
		if (d.getWidth() * 9 < d.getHeight() * 16) {
			// Compress vertically
			w = d.getWidth();
			h = 9.0 * w / 16.0;
		} else {
			// Compress horizontally
			h = d.getHeight();
			w = 16.0 * h / 9.0;
		}
		final int width = (int) w, height = (int) h;
		view.setSize(width,height);
		getContentPane().getSize(d);
		Point p = getContentPane().getLocation();
		p.setLocation(	p.getX() + (d.width - width) / 2, 
						p.getY() + (d.height - height) / 2);
		view.setLocation(p);
	}
	
	/*============================================================
	Private Classes
	============================================================*/

	private final class GraphicsListenerImpl implements GraphicsListener {

		@Override public void onSetShadows(boolean shadows) {}

		@Override public void onSetResolution(Resolution resolution) {
			Graphics.instance().getDimension(d);
			reinstantiate();
		}

		@Override
		public String foo() {
			camera.clearViews();
			return "" + camera.getViews().size() + "\n" + menu.getWorld();
		}
		
	}
	
	private final class EscaperImpl implements Escaper {

		@Override public void escapeFrom(WorldArea world) {
			if (engine.getWorld() == world) try {
				AbstractEnvironment env = new DefaultEnvironment();
				WorldArea w = new DefaultWorldLoader(env,"misc/main").load();
				camera = w.findInstances(Camera.class).iterator().next();
				camera.addView(view);
				menu = createMenu(view);
				w.createInstance(menu);
				world.gotoWorldArea(w);
				world.destroyAll();
			} catch (Exception e) {
				Debug.report(e);
			}
		}
		
	}
	
	private final class WindowListenerImpl implements WindowStateListener {
		
		@Override public void windowStateChanged(WindowEvent e) {
			if (isSet(e.getOldState(),MAXIMIZED) && !isSet(e.getNewState(),MAXIMIZED)) {
				// Reset the window after maximization is cleared
				pack();
				setLocationRelativeTo(null);
			}
		}
		
	}
	
	private final class ComponentListenerImpl implements ComponentListener {

		@Override public void componentResized(ComponentEvent event) {
			if (!isSet(GameWindow.this.getExtendedState(),MAXIMIZED)) {
				setPreferredViewSize(getContentPane().getSize());
			} else {
				Dimension d = new Dimension();
				getContentPane().getSize(d);
				int width = d.width, height = d.height;
				Graphics.instance().getDimension(d);
				if (width > d.width && height > d.height) {
					// Adjust to a proper integer scaling
					double scale = Math.min(height/d.getHeight(), width/d.getWidth());
					int s = (int) scale;
					if (scale - s > 0.95) 
						s++; // OK if still more than 95% of the hud is visible
					d.setSize(s * d.width, s * d.height);
				}
				setPreferredViewSize(d);
			}
		}

		@Override public void componentMoved(ComponentEvent e) {};

		@Override public void componentShown(ComponentEvent e) {};

		@Override public void componentHidden(ComponentEvent e) {};
		
	}

}
